#pragma once 
#include "db.hpp"
#include "online.hpp"

#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHILE 1
#define CHESS_BLACK 2

typedef enum{GAME_OVER,GAME_START} room_statu;

class room{
private:
    uint64_t _room_id;//房间ID
    room_statu _statu;//房间状态
    int _player_count;//房间人数
    uint64_t _white_id;//白棋玩家
    uint64_t _black_id;//黑棋玩家
    user_table* _tb_user;//数据库用户表
    online_manager* _online_user;//在线用户管理
    std::vector<std::vector<int>> _board;
public:
    room(uint64_t room_id,user_table* ut,online_manager* om)
    :_room_id(room_id),_tb_user(ut),_online_user(om),_player_count(0),_statu(GAME_START),
    _board(BOARD_ROW,std::vector<int>(BOARD_COL,0)){
        DBG_LOG("%lu 房间创建成功!!",_room_id);
    }
    ~room(){
        DBG_LOG("%lu 房间销毁成功!!",_room_id);
    }

    uint64_t id(){return _room_id;}
    int player_count(){return _player_count;}
    uint64_t get_white_user(){return _white_id;}
    uint64_t get_black_user(){return _black_id;}
    room_statu statu(){return _statu;}
    void add_white_user(uint64_t uid){_white_id=uid; _player_count++;}
    void add_black_user(uint64_t uid){ _black_id=uid;_player_count++;}

    /*处理下棋动作*/
    Json::Value handler_chess(Json::Value &req){
        Json::Value json_resp=req;
        //1.判断房间中两个玩家是否在线，任意一方不在线，就是另一方胜利
        if(_online_user->is_game_room(_white_id)==false){
            json_resp["result"]=true;
            json_resp["reason"]="对⽅掉线，不战⽽胜！";
            json_resp["winner"]=(Json::UInt64)_black_id;
            return json_resp;
        }
        if(_online_user->is_game_room(_black_id)==false){
            json_resp["result"]=true;
            json_resp["reason"]="对⽅掉线，不战⽽胜！";
            json_resp["winner"]=(Json::UInt64)_white_id;
            return json_resp;
        }
        //2.获取当前走棋位置，判断是否合理(位置是否已经被占用)
        int chess_row=req["row"].asInt();
        int chess_col=req["col"].asInt();
        if(_board[chess_row][chess_col]!=0){
            json_resp["result"]=false;
            json_resp["reason"]="当前棋子已经有了其他棋子!";
            return json_resp;
        }
        //3. 判断是否有玩家胜利(从当前位置开始判断是否存在五子连珠)
        uint64_t cur_uid=req["uid"].asInt64();
        int cur_color=cur_uid==_white_id?CHESS_WHILE:CHESS_BLACK;
        _board[chess_row][chess_col]=cur_color;
        uint64_t winner_id=check_win(chess_row,chess_col,cur_color);
        if(winner_id!=0){
            json_resp["reason"]="五星连珠，战⽆敌";
        }
        json_resp["result"]=true;
        json_resp["winner"]=(Json::UInt64)winner_id;
        return json_resp;
    }

    /*处理聊天动作*/
    Json::Value handler_chat(Json::Value &req){
        Json::Value json_resp=req;
        //检测消息中是否含有敏感词
        std::string msg=req["message"].asString();
        size_t pos=msg.find("垃圾");
        if(pos!=std::string::npos){
            json_resp["result"]=false;
            json_resp["reason"]="消息中包含敏感词，不能发送！";
            return json_resp;
        }

        //返回消息---要广播消息
        DBG_LOG("handler_chat->message:%s",json_resp["message"].asCString());
        json_resp["result"]=true;
        return json_resp;
    }

    /*处理玩家退出房间动作*/
    void handler_exit(uint64_t uid){
        //如果在下棋的时候退出，则另一位玩家获胜，如果是游戏结束后退出，则是正常退出
        Json::Value json_resp;
        if(_statu==GAME_START){
            uint64_t winner_id=uid==_white_id?_black_id:_white_id;
            json_resp["optype"]="put_chess";
            json_resp["result"]=true;
            json_resp["reason"]="对⽅掉线，不战⽽胜！";
            json_resp["room_id"]=(Json::UInt64)_room_id;
            json_resp["uid"]=(Json::UInt64)uid;
            json_resp["winner"]=(Json::UInt64)winner_id;
            json_resp["row"]=-1;
            json_resp["col"]=-1;
            uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
            _tb_user->win(winner_id);
            _tb_user->lose(loser_id);
            _statu=GAME_OVER;
            broadcast(json_resp);
        }
        //房间人数--
        _player_count--;
        DBG_LOG("房间人数:%d",_player_count);
        return;

    }
    /*将指定的信息⼴播给房间中所有玩家*/
    void broadcast(Json::Value &rsp){
        //1.对要响应的信息进行序列化
        std::string body;
        json_util::serialize(rsp,body);
        //2. 获取房间中所有用户的连接
        //3. 发送响应信息
        wsserver_t::connection_ptr wconn=_online_user->get_conn_from_game_room(_white_id);
        if(wconn.get()!=nullptr){
            wconn->send(body);
        }else{
            DBG_LOG("房间-⽩棋玩家连接获取失败");
        }
        wsserver_t::connection_ptr bconn= _online_user->get_conn_from_game_room(_black_id);
        if(bconn.get()!=nullptr){
            bconn->send(body);
        }else{
            DBG_LOG("房间-黑棋玩家连接获取失败");
        }

    }
    /*总的请求处理函数，在函数内部，区分请求类型，根据不同的请求调⽤不同的处理函数，得到响应进⾏⼴播*/
    void handler_request(Json::Value &req){
        //1. 校验房间号是否匹配
        Json::Value json_resp;
        uint64_t room_id=req["room_id"].asInt64();
        if(room_id!=_room_id){
            json_resp["optype"]=req["optype"].asString();
            json_resp["result"]=false;
            json_resp["reason"]="房间号不匹配!!";
            return broadcast(json_resp);

        }
        //2. 根据不同的请求类型调⽤不同的处理函数
        if(req["optype"].asString()=="put_chess"){
            json_resp=handler_chess(req);
            if(json_resp["winner"].asUInt64()!=0){
                uint64_t winner_id=json_resp["winner"].asUInt64();
                uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu=GAME_OVER;
            }
        }else if(req["optype"].asString()=="chat"){
                json_resp=handler_chat(req);
        }else{
            json_resp["optype"]=req["optype"].asString();
            json_resp["reason"]="未知请求类型";
            json_resp["result"]=false;
        }

        return broadcast(json_resp);
    }
private:
    int check_win(int row,int col,int color){
        // 从下棋位置的四个不同⽅向上检测是否出现了5个及以上相同颜⾊的棋⼦（横⾏，纵列，正斜，反斜）
        if(five(row,col,0,1,color) ||
            five(row,col,1,0,color) ||
            five(row,col,1,1,color) ||
            five(row,col,-1,1,color)){
                //任意⼀个⽅向上出现了true也就是五星连珠，则设置返回值
                return color==CHESS_WHILE?_white_id:_black_id;
            }
            return 0;
    }

    bool five(int row,int col,int row_off,int col_off,int color){
        int count=1;
        //row和col是下棋位置， row_off和col_off是偏移量，也是⽅向
        int search_row=row+row_off;
        int search_col=col+col_off;
        while(search_row<BOARD_ROW && search_row>=0 
        && search_col<BOARD_COL && search_col>=0
        && _board[search_row][search_col]==color){
            //同⾊棋⼦数量++
            count++;
            //检索位置继续向后偏移
            search_row+=row_off;
            search_col+=col_off;

        }
        search_row=row-row_off;
        search_col=col-col_off;
        while(search_row<BOARD_ROW && search_row>=0 
        && search_col<BOARD_COL && search_col>=0
        && _board[search_row][search_col]==color){
            count++;
            search_row-=row_off;
            search_col-=col_off;
        }
        // DBG_LOG("count:%d",count);
        return (count>=5);
    }
};

using room_ptr=std::shared_ptr<room>;

class room_manager{
private:
    uint64_t _next_rid;//房间ID计数器
    std::mutex _mutex;
    user_table* _tb_user;
    online_manager* _online_user;
    std::unordered_map<uint64_t,room_ptr> _rooms;//房间rid和room_ptr的映射关系
    std::unordered_map<uint64_t,uint64_t> _users;//用户uid和房间rid的映射关系
public:
    /*初始化房间ID计数器*/
    room_manager(user_table* ut,online_manager* om):_tb_user(ut),_online_user(om),_next_rid(1){
        DBG_LOG("房间管理模块初始化完毕!!");
    }
    ~room_manager(){
        DBG_LOG("房间管理模块即将销毁!!");
    }
    //为两个⽤⼾创建房间，并返回房间的智能指针管理对象
    room_ptr create_room(uint64_t uid1,uint64_t uid2){
        //两个用户在大厅中进行对战匹配，匹配成功后创建房间
        //1.校验两个用户都是否还在游戏大厅,只有都在才能创建房间
        if(_online_user->is_game_hall(uid1)==false){
            DBG_LOG("%d :不在游戏大厅！创建游戏房间失败!!",uid1);
            return room_ptr();
        }
        if(_online_user->is_game_hall(uid2)==false){
            DBG_LOG("%d :不在游戏大厅！创建游戏房间失败!!",uid2);
            return room_ptr();
        }
        //2. 创建房间，将⽤⼾信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);
        room_ptr rp(new room(_next_rid,_tb_user,_online_user));
        rp->add_white_user(uid1);
        rp->add_black_user(uid2);
        //3. 将信息管理起来
        _rooms.insert(std::make_pair(_next_rid,rp));
        _users.insert(std::make_pair(uid1,_next_rid));
        _users.insert(std::make_pair(uid2,_next_rid));
        _next_rid++;
        return rp;
    }
    /*通过房间ID获取房间信息*/
    room_ptr get_room_by_rid(uint64_t rid){
        std::unique_lock<std::mutex> lock(_mutex);
        auto it=_rooms.find(rid);
        if(it==_rooms.end()){
            return room_ptr();
        }
        return it->second;
    }
    /*通过⽤⼾ID获取房间信息*/
    room_ptr get_room_by_uid(uint64_t uid){
        std::unique_lock<std::mutex> lock(_mutex);
        //1. 通过⽤⼾ID获取房间ID
        auto it1=_users.find(uid);
        if(it1==_users.end()){
            return room_ptr();
        }
        //2. 通过房间ID获取房间信息
        auto it2=_rooms.find(it1->second);
        if(it2==_rooms.end()){
            return room_ptr();
        }
        return it2->second;
    }
    /*通过房间ID销毁房间*/
    void remove_room(uint64_t rid){
        //因为房间信息，是通过shared_ptr在_rooms中进⾏管理，因此只要将shared_ptr从_rooms中移除
        //则shared_ptr计数器==0，外界没有对房间信息进⾏操作保存的情况下就会释放
        //1. 通过房间ID，获取房间信息
        room_ptr rp=get_room_by_rid(rid);
        if(rp.get()==nullptr){
            DBG_LOG("rp为空");
            return;
        }
        //2. 通过房间信息，获取房间中所有⽤⼾的ID
        uint64_t uid1=rp->get_white_user();
        uint64_t uid2=rp->get_black_user();
        //3. 移除房间管理中的用户信息
        std::unique_lock<std::mutex> lock(_mutex);
        _users.erase(uid1);
        _users.erase(uid2);
        //4. 移除房间管理信息
        _rooms.erase(rid);
    }
    /*删除房间中指定⽤⼾，如果房间中没有⽤⼾了，则销毁房间，⽤⼾连接断开时被调⽤*/
    void remove_room_user(uint64_t uid){
        room_ptr rp=get_room_by_uid(uid);
        if(rp.get()==nullptr){
            DBG_LOG("rp为空");
            return;
        }
        //4. 移除房间管理信息
        rp->handler_exit(uid);
        if(rp->player_count()==0){
            remove_room(rp->id());
        }
    }
};